package com.basin.common.mybatisplus.base;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.repository.CrudRepository;
import com.basin.common.core.exception.BasinAssert;
import com.basin.common.core.exception.BasinException;
import com.basin.common.core.util.PrintUtil;
import com.basin.common.mybatisplus.annotations.Search;
import com.basin.common.mybatisplus.annotations.Unique;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.mapstruct.factory.Mappers;
import org.springframework.core.ResolvableType;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Stream;

/**
 * @author: Sam ZHONG | sammy1997@aliyun.com
 */
@Slf4j
public abstract class BaseServiceImpl<DTO extends BasePage, VO, T extends BaseEntity, REPO extends CrudRepository<?, T>> implements BaseService<DTO, VO> {
    protected REPO repository;
    protected BaseConvert<T, DTO, VO> converter;

    @Resource
    public void setRepository(REPO repository) {
        this.repository = repository;
    }

    @SneakyThrows
    @SuppressWarnings("unchecked")
    public BaseServiceImpl() {
        ResolvableType resolvableType = ResolvableType.forClass(getClass())
                .getSuperType()
                .getGeneric(2);
        Class<?> clazz = resolvableType.resolve();
        if (ObjUtil.isNull(clazz)) {
            throw new BasinException("初始化基础业务实现类异常, 获取实体类对象为空");
        }
        String convertName = clazz.getName()
                .replace("entity", "convert")
                .replace("Entity", "Convert");
        Class<?> convertClazz = Class.forName(convertName);
        this.converter = (BaseConvert<T, DTO, VO>) Mappers.getMapper(convertClazz);
    }

    @Override
    public boolean save(DTO dto) {
        T entity = converter.dto2Entity(dto);
        uniqueCheck(entity, false);
        return this.repository.save(entity);
    }

    @Override
    public boolean update(DTO dto) {
        T entity = converter.dto2Entity(dto);
        // 数据是否存在
        T dbEnt = this.repository.getById(entity.getId());
        BasinAssert.isTrue(ObjUtil.isNotNull(dbEnt), "数据不存在");
        // 传过来的实体存在空字段 则尝试用数据库的实体对应的字段值填入 方便校验
        fillNull(entity, dbEnt);
        uniqueCheck(entity, true);
        return this.repository.updateById(entity);
    }

    @Override
    public boolean delete(Long id) {
        return this.repository.removeById(id);
    }

    @Override
    public boolean saveBatch(List<DTO> dtoList) {
        List<T> entityList = new ArrayList<>(dtoList.size());
        dtoList.forEach(dto -> {
            T entity = converter.dto2Entity(dto);
            uniqueCheck(entity, false);
            entityList.add(entity);
        });
        return this.repository.saveBatch(entityList, entityList.size());
    }

    @Override
    public boolean updateBatch(List<DTO> dtoList) {
        List<T> entityList = new ArrayList<>(dtoList.size());
        dtoList.forEach(dto -> {
            T entity = converter.dto2Entity(dto);
            T dbEnt = this.repository.getById(entity.getId());
            BasinAssert.isTrue(ObjUtil.isNotNull(dbEnt), "数据不存在, id=%s".formatted(entity.getId()));
            fillNull(entity, dbEnt);
            uniqueCheck(entity, true);
            entityList.add(entity);
        });
        return this.repository.updateBatchById(entityList, entityList.size());
    }

    @Override
    public boolean deleteBatch(Collection<Long> ids) {
        return this.repository.removeByIds(ids);
    }

    @Override
    public VO detail(Long id) {
        T entity = this.repository.getOptById(id)
                .orElseThrow(() -> new BasinException("数据不存在"));
        return converter.entity2Vo(entity);
    }

    @Override
    public IPage<VO> page(DTO dto) {
        QueryWrapper<T> queryWrapper = Wrappers.query();
        queryWrapper.orderByDesc("update_time");
        Field[] fields = dto.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Search search = field.getAnnotation(Search.class);
            if (search == null) continue;
            String fieldName = StrUtil.toUnderlineCase(field.getName());
            String columnName = search.column();
            if (StrUtil.isBlank(columnName)) {
                columnName = fieldName;
            }
            Search.SearchType searchType = search.value();
            Object fieldValue;
            try {
                fieldValue = field.get(dto);
            } catch (IllegalAccessException e) {
                fieldValue = null;
                log.error("反射获取查询条件值异常[fieldName={}]", fieldName, e);
            }
            if (ObjUtil.isNotNull(fieldValue)) {
                switch (searchType) {
                    case EQ -> queryWrapper.eq(ObjUtil.isNotNull(fieldValue), columnName, fieldValue);
                    case LIKE -> queryWrapper.like(ObjUtil.isNotNull(fieldValue), columnName, fieldValue);
                    case IN -> {
                        if (fieldValue instanceof Collection<?> valCol) {
                            queryWrapper.in(CollUtil.isNotEmpty(valCol), columnName, valCol);
                        }
                    }
                    case BETWEEN -> {
                        if (fieldValue instanceof Object[] array) {
                            if (array.length != 2) {
                                throw new BasinException("between查询条件需要传递数组长度为2");
                            }
                            queryWrapper.between(columnName, array[0], array[1]);
                        } else {
                            throw new BasinException("between查询条件需要传递数组");
                        }
                    }
                }
            }
        }

        IPage<T> page = this.repository.page(Page.of(dto.getCurrent(), dto.getSize()), queryWrapper);
        IPage<VO> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        List<VO> tenantVOList = converter.entityList2VoList(page.getRecords());
        voPage.setRecords(tenantVOList);
        return voPage;
    }

    /**
     * @description: uniqueCheck
     * @param: entity - [entity]
     * @param: isUpdate - [isUpdate]
     * @author: Sammy ZHONG | sammy1997@aliyun.com
     * @date: 2025/5/12 21:11
     */
    private void uniqueCheck(T entity, boolean isUpdate) {
        // 唯一性校验
        Field[] fields = entity.getClass().getDeclaredFields();
        // 分组校验
        Map<String, List<Tuple>> fieldMap = new HashMap<>(fields.length);
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                String fieldName = StrUtil.toUnderlineCase(field.getName());
                Object fieldValue = field.get(entity);
                Unique annotation = field.getAnnotation(Unique.class);
                if (annotation != null) {
                    // 有两组注解属性
                    String[] groupGroArr = annotation.group();
                    String[] groupValArr = annotation.value();
                    String[] groupArr = Stream.concat(Arrays.stream(groupGroArr), Arrays.stream(groupValArr))
                            .toArray(String[]::new);
                    if (ArrayUtil.isNotEmpty(groupArr)) {
                        for (String group : groupArr) {
                            List<Tuple> fieldList = new ArrayList<>(1);
                            fieldList.add(new Tuple(fieldName, fieldValue));
                            fieldMap.merge(group, fieldList, (fields1, fields2) -> {
                                fields1.addAll(fields2);
                                return fields1;
                            });
                        }
                    } else {
                        QueryWrapper<T> queryWrapper = new QueryWrapper<T>();
                        queryWrapper.eq(ObjUtil.isNotNull(fieldValue), fieldName, fieldValue);
                        T one = this.repository.getOne(queryWrapper);
                        if (!isUpdate) {
                            BasinAssert.isTrue(ObjUtil.isNull(one), "数据项重复[%s=%s]".formatted(fieldName, fieldValue));
                        } else {
                            if (!one.getId().equals(entity.getId())) {
                                BasinAssert.isTrue(ObjUtil.isNull(one), "数据项重复[%s=%s]".formatted(fieldName, fieldValue));
                            }
                        }
                    }
                }
            }
            if (CollUtil.isNotEmpty(fieldMap)) {
                for (Map.Entry<String, List<Tuple>> entry : fieldMap.entrySet()) {
                    String key = entry.getKey();
                    List<Tuple> fieldList = entry.getValue();
                    QueryWrapper<T> queryWrapper = new QueryWrapper<>();
                    for (Tuple field : fieldList) {
                        String fieldName = field.get(0);
                        Object fieldValue = field.get(1);
                        queryWrapper.eq(ObjUtil.isNotNull(fieldValue), fieldName, fieldValue);
                    }
                    T one = this.repository.getOne(queryWrapper);
                    if (!isUpdate) {
                        BasinAssert.isTrue(ObjUtil.isNull(one), "[group=%s]数据项重复[%s]".formatted(key, PrintUtil.tuplePrint(fieldList)));
                    } else {
                        if (!one.getId().equals(entity.getId())) {
                            BasinAssert.isTrue(ObjUtil.isNull(one), "[group=%s]数据项重复[%s]".formatted(key, PrintUtil.tuplePrint(fieldList)));
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            log.error("base serviceImpl func校验方法异常:{}", e.getMessage(), e);
            throw new BasinException("校验方法异常=%s".formatted(e.getMessage()));
        }
    }

    private void fillNull(T argEnt, T dbEnt) {
        Class<?> clazz = argEnt.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            // 设置字段可访问
            field.setAccessible(true);
            try {
                Object argValue = field.get(argEnt);
                if (argValue == null) {
                    Object dbValue = field.get(dbEnt);
                    // 设置argEnt的字段值
                    field.set(argEnt, dbValue);
                }
            } catch (IllegalAccessException e) {
                log.error("更新实体与数据实体映射操作异常", e);
                throw new BasinException(e);
            }
        }
    }
}
